#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/epoll.h>
#include <infiniband/verbs.h>
#include <rdma/rdma_cma.h>

#include "iser.h"
#include "block.h"
#include "core.h"
#include "net_vip.h"
#include "chap.h"

extern int membuf_num;
extern size_t membuf_size;

static struct iscsi_key login_keys[] = {
        {"InitiatorName", 0, 0, 0, NULL},
        {"InitiatorAlias", 0, 0, 0, NULL},
        {"SessionType", 0, 0, 0, NULL},
        {"TargetName", 0, 0, 0, NULL},
        {NULL, 0, 0, 0, NULL},
};

static char *iser_text_next_key(char **data, int *datasize, char **value)
{
        char *key, *p, *q;
        int size = *datasize;

        key = p = *data;
        for (; size > 0 && *p != '='; p++, size--)
                ;
        if (!size)
                return NULL;
        *p++ = 0;
        size--;

        for (q = p; size > 0 && *p != 0; p++, size--)
                ;
        if (!size)
                return NULL;
        p++;
        size--;

        *data = p;
        *value = q;
        *datasize = size;

        return key;
}

static char *iser_text_key_find(char *data, int datasize, char *searchKey)
{
        int keylen = strlen(searchKey);
        char *key, *value;

        while (1) {
                for (key = data; datasize > 0 && *data != '='; data++, datasize--)
                        ;
                if (!datasize)
                        return NULL;
                data++;
                datasize--;

                for (value = data; datasize > 0 && *data != 0; data++, datasize--)
                        ;
                if (!datasize)
                        return NULL;
                data++;
                datasize--;

                if (keylen == value - key - 1
                    && !strncmp(key, searchKey, keylen))
                        return value;
        }
}

static void iser_text_key_add(struct iscsi_conn *iscsi_conn,
                              struct iser_pdu *pdu,
                              char *key, char *value)
{
        struct iser_conn *iser_conn = ISER_CONN(iscsi_conn);
        int keylen = strlen(key);
        int valuelen = strlen(value);
        int len = keylen + valuelen + 2;
        char *buffer;

        if (pdu->membuf.size + len > iser_conn->ssize) {
                DWARN("Dropping key: %s=%s, pdu_sz:%d, key_sz:%d, ssize:%d\n",
                            key, value, pdu->membuf.size, len, iser_conn->ssize);
                return;
        }

        DINFO("%s=%s, offset:%d\n", key, value, pdu->membuf.size);

        YASSERT(pdu->membuf.addr);
        buffer = pdu->membuf.addr + pdu->membuf.size;
        pdu->membuf.size += len;

        strcpy(buffer, key);
        buffer += keylen;
        *buffer++ = '=';
        strcpy(buffer, value);
}

static void iser_text_key_add_reject(struct iscsi_conn *iscsi_conn,
                                     struct iser_pdu *pdu,
                                     char *key)
{
        iser_text_key_add(iscsi_conn, pdu, key, "Reject");
}

static int account_empty(struct iscsi_conn *iscsi_conn, int dir)
{
        char pass[MAX_BUF_LEN];

        UNIMPLEMENTED(__WARN__);
        /* ======iser chap need implement====== */
        if (!iscsi_conn->target)
                return 1;

        //return 1;
        DWARN("iscsi iser this is a test on account\n");
        memset(pass, 0x00, sizeof(pass));
        return cops->account_query(iscsi_conn, dir, pass, pass) ? 1 : 0;
}

static void iser_login_security_scan(struct iscsi_conn *iscsi_conn,
                                     struct iser_pdu *rx_pdu,
                                     struct iser_pdu *tx_pdu)
{
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        char *key, *value, *data, *nextValue;
        int datasize;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        while ((key = iser_text_next_key(&data, &datasize, &value))) {
                if (param_index_by_name(key, login_keys, NULL) == 0)
                        ;
                else if (!strcmp(key, "AuthMethod")) {
                        do {
                                nextValue = strchr(value, ',');
                                if (nextValue)
                                        *nextValue++ = 0;

                                if (!strcmp(value, "None")) {
                                        if (!account_empty(iscsi_conn, AUTH_DIR_INCOMING))
                                                continue;
                                        iscsi_conn->auth_method = AUTH_NONE;
                                        iser_text_key_add(iscsi_conn, tx_pdu, key, "None");
                                        break;
                                } else if (!strcmp(value, "CHAP")) {
                                        if (account_empty(iscsi_conn, AUTH_DIR_INCOMING))
                                                continue;
                                        iscsi_conn->auth_method = AUTH_CHAP;
                                        iser_text_key_add(iscsi_conn, tx_pdu, key, "CHAP");
                                        break;
                                }
                        } while ((value = nextValue));

                        DWARN("iscsi iser this is a test begin\n");
                        if (iscsi_conn->auth_method == AUTH_UNKNOWN)
                                iser_text_key_add_reject(iscsi_conn, tx_pdu, key);
                } else
                        iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood");
        }
        DWARN("iscsi iser this is a test begin1\n");
        if (iscsi_conn->auth_method == AUTH_UNKNOWN) {
                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                rsp_bhs->status_detail = ISCSI_STATUS_AUTH_FAILED;
                iscsi_conn->state = STATE_EXIT;
        }
}

static void iser_login_security_done(struct iscsi_conn *iscsi_conn,
                                     struct iser_pdu *rx_pdu,
                                     struct iser_pdu *tx_pdu)
{
        struct iscsi_login_req_hdr *req_bhs = (struct iscsi_login_req_hdr *)rx_pdu->bhs;
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        struct iscsi_session *session;

        if (!iscsi_conn->tid)
                return;

        session = session_find_by_name(iscsi_conn->initiator, req_bhs->sid, &iscsi_conn->target->fileid);
        if (session) {
                if (!req_bhs->sid.id.tsih) {
                        struct iscsi_conn *ent, *next;
                        struct iser_conn *c = NULL;
			int ret;

			if (core_self()){
				/* do session reinstatement */
				list_for_each_entry_safe(ent, next, &session->conn_list, entry) {
					c = container_of(ent, struct iser_conn, h);
					break;
				}

				if (c->yield) {
                                        //YASSERT(0);
                                        rsp_bhs->status_class = ISCSI_STATUS_TARGET_ERR;
                                        rsp_bhs->status_detail = ISCSI_STATUS_SVC_UNAVAILABLE;
                                        iscsi_conn->state = STATE_EXIT;
                                        return;
                                }
					
				DWARN("session need reuse\n");
				c->sched_task = schedule_task_get();
				c->yield = 1;
				ret = schedule_yield("session reuse", NULL, NULL);
				if (ret) {
					DWARN("session reuse yield failed %s\n", strerror(ret));
				}

				/*c->yield = 0;
				session = NULL;*/
				return ;
			}

                } else if (req_bhs->sid.id.tsih != session->sid.id.tsih) {
                        /* fail the login */
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                } else if (conn_find(session, iscsi_conn->cid)) {
                        /* do connection reinstatement */
                }

                /* Currently not support session reuse */
                YASSERT(0);

                /* add a new connection to the session */
                UNIMPLEMENTED(__WARN__);
                /*
                if (session)
                        conn_add_to_session(iscsi_conn, session);
                */
                UNIMPLEMENTED(__WARN__);
        } else {
                if (req_bhs->sid.id.tsih) {
                        /* fail the login */
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_SESSION_NOT_FOUND;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }
                /*
                 * We do nothing here and instantiate a new session
                 * later at login_finish().
                 */
        }
}

static void iser_login_oper_scan(struct iscsi_conn *iscsi_conn,
                                 struct iser_pdu *rx_pdu,
                                 struct iser_pdu *tx_pdu)
{
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        char *key, *value, *data;
        int datasize, idx, is_rdma = 0;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        while ((key = iser_text_next_key(&data, &datasize, &value))) {
                DINFO("%s=%s\n", key, value);
                if (param_index_by_name(key, login_keys, NULL) == 0)
                        ;
                else if (!strcmp(key, "AuthMethod"))
                        ;
                else if (param_index_by_name(key, session_keys, &idx) == 0) {
                        int ret;
                        unsigned int val;
                        char buf[32];

                        if (idx == key_max_xmit_data_length) {
                                iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood");
                                continue;
                        }

                        if (idx == key_max_recv_data_length)
                                idx = key_max_xmit_data_length;

                        if (idx == key_rdma_extensions)
                                is_rdma = 1;

                        if (param_str_to_val(session_keys, idx, value, &val) < 0) {
                                if (iscsi_conn->session_param[idx].state
                                    == KEY_STATE_START) {
                                        iser_text_key_add_reject(iscsi_conn, tx_pdu, key);
                                        continue;
                                } else {
                                        rsp_bhs->status_class =
                                                ISCSI_STATUS_INITIATOR_ERR;
                                        rsp_bhs->status_detail =
                                                ISCSI_STATUS_INIT_ERR;
                                        iscsi_conn->state = STATE_EXIT;
                                        goto out;
                                }
                        }

                        ret = param_check_val(session_keys, idx, &val);
                        if (ret) {
                                iser_text_key_add_reject(iscsi_conn, tx_pdu, key);
                                continue;
                        }

                        param_set_val(session_keys, iscsi_conn->session_param, idx, &val);


                        switch (iscsi_conn->session_param[idx].state) {
                        case KEY_STATE_START:
                                if (idx >= session_key_last)
                                        break;
                                memset(buf, 0, sizeof(buf));
                                param_val_to_str(session_keys, idx, val, buf);
                                iser_text_key_add(iscsi_conn, tx_pdu, key, buf);
                                break;
                        case KEY_STATE_REQUEST:
                                if (val != iscsi_conn->session_param[idx].val) {
                                        rsp_bhs->status_class =
                                                ISCSI_STATUS_INITIATOR_ERR;
                                        rsp_bhs->status_detail =
                                                ISCSI_STATUS_INIT_ERR;
                                        iscsi_conn->state = STATE_EXIT;
                                        DWARN("%s %u %u\n",
                                                    key, val,
                                                    iscsi_conn->session_param[idx].val);
                                        goto out;
                                }
                                break;
                        case KEY_STATE_DONE:
                                break;
                        }
                        iscsi_conn->session_param[idx].state = KEY_STATE_DONE;
                } else
                        iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood");
        }

        if (is_rdma) {
                /* do not try to do digests, not supported in iser */
                iscsi_conn->session_param[key_header_digest].val = DIGEST_NONE;
                iscsi_conn->session_param[key_data_digest].val = DIGEST_NONE;
        } else {
                /* do not offer RDMA, initiator must explicitly request */
                iscsi_conn->session_param[key_rdma_extensions].val = 0;
        }

out:
        return;
}

static int iser_login_check_params(struct iscsi_conn *iscsi_conn,
                                   struct iser_pdu *pdu)
{
        struct iscsi_param *p = iscsi_conn->session_param;
        char buf[32];
        int i, cnt;

        for (i = 0, cnt = 0; session_keys[i].name; i++) {
                if (p[i].state == KEY_STATE_START && p[i].val != session_keys[i].def) {
                        if (iscsi_conn->state == STATE_LOGIN) {
                                if (i >= session_key_last) {
                                        if (p[i].val > session_keys[i].max)
                                                p[i].val = session_keys[i].max;
                                        p[i].state = KEY_STATE_DONE;
                                        continue;
                                }
                                if (i == key_max_xmit_data_length)
                                        continue;
                                if (p[key_rdma_extensions].val == 1) {
                                        if (i == key_max_recv_data_length)
                                                continue;
                                } else {
                                        if (i >= key_rdma_extensions)
                                                continue;
                                }
                                memset(buf, 0, sizeof(buf));
                                param_val_to_str(session_keys, i, p[i].val, buf);
                                iser_text_key_add(iscsi_conn, pdu, session_keys[i].name, buf);
                                p[i].state = KEY_STATE_REQUEST;
                        }
                        cnt++;
                }
        }

        return cnt;
}

static int iser_chap_initiator_auth_create_challenge(struct iscsi_conn *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu)      //todo. merge to iscsi.
{
        int ret, i;
        char *value, *p;
        char text[CHAP_CHALLENGE_MAX * 2 + 8];
        static int chap_id;
        char *data;
        int datasize;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        value = iser_text_key_find(data, datasize, "CHAP_A");
        if (!value) {
                ret = CHAP_INITIATOR_ERROR;
                GOTO(err_ret, ret);
        }

        while ((p = strsep(&value, ","))) {
                if (!strcmp(p, "5")) {
                        iscsi_conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5;
                        iscsi_conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
                        break;
                } else if (!strcmp(p, "7")) {
                        iscsi_conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
                        iscsi_conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
                        break;
                }
        }
        if (!p) {
                ret = CHAP_INITIATOR_ERROR;
                GOTO(err_ret, ret);
        }

        iscsi_conn->auth.chap.id = ++chap_id;
        sprintf(text, "%u", (unsigned char)iscsi_conn->auth.chap.id);

        iser_text_key_add(iscsi_conn, tx_pdu, "CHAP_A", p);
        iser_text_key_add(iscsi_conn, tx_pdu, "CHAP_I", text);

        iscsi_conn->auth.chap.challenge_size = (random() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
        iscsi_conn->auth.chap.challenge = malloc(iscsi_conn->auth.chap.challenge_size);
        if (!iscsi_conn->auth.chap.challenge) {
                ret = CHAP_TARGET_ERROR;
                GOTO(err_ret, ret);
        }

        p = text;
        strcpy(p, "0x");
        p += 2;
        for (i = 0; i < iscsi_conn->auth.chap.challenge_size; ++i) {
                iscsi_conn->auth.chap.challenge[i] = rand();
                sprintf(p, "%.2hhx", iscsi_conn->auth.chap.challenge[i]);
                p += 2;
        }

        iser_text_key_add(iscsi_conn, tx_pdu, "CHAP_C", text);

        return 0;
err_ret:
        return ret;
}

static int iser_chap_initiator_auth_check_response(struct iscsi_conn *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu)
{
        char name[MAX_NAME_LEN], *value;
        u8 *his_digest = NULL, *our_digest = NULL;
        int digest_len = 0, ret = 0, encoding_format;
        char pass[MAX_NAME_LEN];
        char *data;
        int datasize = 0;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        value = iser_text_key_find(data, datasize, "CHAP_N");
        if (!value) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        YASSERT(strlen(value) < MAX_NAME_LEN);
        strcpy(name, value);
        DBUG("CHAP_N: %s\n", value);

        /* find password in configure file base on initiator name */
        memset(pass, 0x00, sizeof(pass));
        if (iscsi_conn->session_type == SESSION_NORMAL) {
                if (account_empty(iscsi_conn, AUTH_DIR_INCOMING)) {
                        goto finish;
                }
        }

        ret = cops->account_query(iscsi_conn, AUTH_DIR_INCOMING, name, pass);
        if (unlikely(ret)) {
                DWARN("CHAP initiator auth: "
                      "No valid user/pas combination for initiator %s found\n",
                      iscsi_conn->initiator);
                ret = CHAP_AUTH_ERROR;
                goto out;
        }

        if (iscsi_conn->session_type == SESSION_NORMAL) {
                if (strcmp(name, value)) {
                        DBUG("CHAP initiator auth: "
                              "No valid user/pas combination for initiator %s found\n",
                              iscsi_conn->initiator);
                        ret = CHAP_AUTH_ERROR;
                        goto out;
                }
        }

        value = iser_text_key_find(data, datasize, "CHAP_R");
        if (!value) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }
        DBUG("CHAP_R: %s\n", value);

        encoding_format = chap_check_encoding_format(value);
        if (encoding_format < 0) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }
        DBUG("encoding_format: %d\n", encoding_format);

        switch (iscsi_conn->auth.chap.digest_alg) {
        case CHAP_DIGEST_ALG_MD5:
                digest_len = CHAP_MD5_DIGEST_LEN;
                break;
        case CHAP_DIGEST_ALG_SHA1:
                digest_len = CHAP_SHA1_DIGEST_LEN;
                break;
        default:
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        his_digest = malloc(digest_len);
        if (!his_digest) {
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        our_digest = malloc(digest_len);
        if (!our_digest) {
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        ret = chap_decode_string(value, his_digest, digest_len, encoding_format);
        if (unlikely(ret)) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        switch (iscsi_conn->auth.chap.digest_alg) {
        case CHAP_DIGEST_ALG_MD5:
                chap_calc_digest_md5(iscsi_conn->auth.chap.id, pass, strlen(pass),
                                     iscsi_conn->auth.chap.challenge,
                                     iscsi_conn->auth.chap.challenge_size,
                                     our_digest);
                break;
        case CHAP_DIGEST_ALG_SHA1:
                chap_calc_digest_sha1(iscsi_conn->auth.chap.id, pass, strlen(pass),
                                      iscsi_conn->auth.chap.challenge,
                                      iscsi_conn->auth.chap.challenge_size,
                                      our_digest);
                break;
        default:
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        if (iscsi_conn->session_type == SESSION_DISCOVERY) {
                strcpy(iscsi_conn->auth_username, name);
                strcpy(iscsi_conn->auth_password, value);
        } else if (memcmp(our_digest, his_digest, digest_len)) {
                ret = CHAP_AUTH_ERROR;
                goto out;
        }

finish:
        iscsi_conn->state = CHAP_AUTH_STATE_RESPONSE;

        ret = 0;
out:
        free(his_digest);
        free(our_digest);
        return ret;
}

static int iser_chap_target_auth_create_response(struct iscsi_conn *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) 
{
        char chap_id, *value, *response = NULL;
        u8 *challenge = NULL, *digest = NULL;
        int encoding_format, response_len;
        int challenge_len = 0, digest_len = 0, ret = 0;
        char pass[MAX_NAME_LEN], name[MAX_NAME_LEN];
        char *data;
        int datasize;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        value = iser_text_key_find(data, datasize, "CHAP_I");
        if (!value) {
                /* Initiator doesn't want target auth ?! */
                iscsi_conn->state = STATE_SECURITY_DONE;
                ret = 0;
                goto out;
        }

        chap_id = strtol(value, &value, 10);

        memset(pass, 0x00, sizeof(pass));
        memset(name, 0x00, sizeof(name));

        /* Get name and pass from configure file */
        ret = cops->account_query(iscsi_conn, AUTH_DIR_OUTGOING, name, pass);
        if (unlikely(ret)) {
                DWARN("CHAP target auth : no outgoing credentials configure");
                ret = CHAP_AUTH_ERROR;
                goto out;
        }

        value = iser_text_key_find(data, datasize, "CHAP_C");
        if (!value) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        encoding_format = chap_check_encoding_format(value);
        if (encoding_format < 0) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        DBUG("encoding_format: %d\n", encoding_format);

        ret = chap_alloc_decode_buffer(value, &challenge, encoding_format);
        if (ret <= 0)
                goto out;
        else if (ret > 1024) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        challenge_len = ret;
        ret = 0;

        switch (iscsi_conn->auth.chap.digest_alg) {
        case CHAP_DIGEST_ALG_MD5:
                digest_len = CHAP_MD5_DIGEST_LEN;
                break;
        case CHAP_DIGEST_ALG_SHA1:
                digest_len = CHAP_SHA1_DIGEST_LEN;
                break;
        default:
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        if (encoding_format == HEX_FORMAT)
                response_len = 2 * digest_len;
        else
                response_len = ((digest_len - 1) / 3 + 1) * 4;
        /* "0x" / "0b" and '\0' */
        response_len += 3;

        digest = malloc(digest_len);
        if (!digest) {
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        response = malloc(response_len);
        if (!response) {
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        ret = chap_decode_string(value, challenge, challenge_len, encoding_format);
        if (unlikely(ret)) {
                ret = CHAP_INITIATOR_ERROR;
                goto out;
        }

        /*
         * CHAP challenges MUST NOT be reused - RFC3720
         */
        if (challenge_len == iscsi_conn->auth.chap.challenge_size) {
                if (!memcmp(challenge, iscsi_conn->auth.chap.challenge, challenge_len)) {
                        ret = CHAP_INITIATOR_ERROR;
                        goto out;
                }
        }

        switch (iscsi_conn->auth.chap.digest_alg) {
        case CHAP_DIGEST_ALG_MD5:
                chap_calc_digest_md5(chap_id, pass, strlen(pass),
                                     challenge, challenge_len, digest);
                break;
        case CHAP_DIGEST_ALG_SHA1:
                chap_calc_digest_sha1(chap_id, pass, strlen(pass),
                                      challenge, challenge_len, digest);
                break;
        default:
                ret = CHAP_TARGET_ERROR;
                goto out;
        }

        memset(response, 0x00, response_len);
        chap_encode_string(digest, digest_len, response, encoding_format);
        iser_text_key_add(iscsi_conn, tx_pdu, "CHAP_N", name);
        iser_text_key_add(iscsi_conn, tx_pdu, "CHAP_R", response);

        free(response);

        iscsi_conn->state = STATE_SECURITY_DONE;
        ret = 0;
out:
        return ret;
}

int iser_exec_auth_chap(struct iscsi_conn *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu)
{
        int ret = 0;

        switch (iscsi_conn->auth_state) {
        case CHAP_AUTH_STATE_START:
                ret = iser_chap_initiator_auth_create_challenge(iscsi_conn, rx_pdu, tx_pdu);
                break;
        case CHAP_AUTH_STATE_CHALLENGE:
                ret = iser_chap_initiator_auth_check_response(iscsi_conn, rx_pdu, tx_pdu);
                if (unlikely(ret))
                        break;
                /* fall through */
        case CHAP_AUTH_STATE_RESPONSE:
                ret = iser_chap_target_auth_create_response(iscsi_conn, rx_pdu, tx_pdu);
                break;
        default:
                DERROR("BUG: unknow auth_state %ld\n", iscsi_conn->auth_state);
                ret = CHAP_TARGET_ERROR;
                break;
        }

        return ret;
}

static int iser_login_auth_exec(struct iscsi_conn *iscsi_conn,
                                struct iser_pdu *rx_pdu,
                                struct iser_pdu *tx_pdu)
{
        int res;
        
        switch (iscsi_conn->auth_method) {
        case AUTH_CHAP:
                res = iser_exec_auth_chap(iscsi_conn, rx_pdu, tx_pdu);
                break;
        case AUTH_NONE:
                res = 0;
                break;
        default:
                DERROR("Unknown auth. method %d\n", iscsi_conn->auth_method);
                res = -3;
        }

        return res;
}

struct target_info {
        char target_name[128];
        struct iscsi_target *target;
        int ret;
};

static void iser_target_create(void *arg)
{
        int ret;
        struct target_info *tgt_info = arg;
        struct iscsi_target *target;

        ret = target_alloc_by_name(tgt_info->target_name, &target);
        if (ret) {
                tgt_info->ret = ret;
                return;
        }
        target->sess_param.initial_r2t = 1;
        target->sess_param.immediate_data = 0;

        tgt_info->ret = 0;
        tgt_info->target = target;

}

static void iser_login_start(struct iscsi_conn *iscsi_conn,
                             struct iser_pdu *rx_pdu,
                             struct iser_pdu *tx_pdu)
{
        int ret, reason;
        struct iscsi_login_req_hdr *req_bhs = (struct iscsi_login_req_hdr *)rx_pdu->bhs;
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        char *req_data = rx_pdu->membuf.addr;
        int req_datasize = rx_pdu->membuf.size;
        char *name, *alias, *session_type, *target_name;
        char buf[NI_MAXHOST + NI_MAXSERV + 4];

        (void) buf;
        (void) reason;
        iscsi_conn->cid = be16_to_cpu(req_bhs->cid);
        memcpy(iscsi_conn->sid.id.isid, req_bhs->sid.id.isid, sizeof(req_bhs->sid.id.isid));
        iscsi_conn->sid.id.tsih = req_bhs->sid.id.tsih;

        if (!sid64(iscsi_conn->sid.id.isid, iscsi_conn->sid.id.tsih)) {
                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                rsp_bhs->status_detail = ISCSI_STATUS_MISSING_FIELDS;
                iscsi_conn->state = STATE_EXIT;
                return;
        }

        name = iser_text_key_find(req_data, req_datasize, "InitiatorName");
        if (!name) {
                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                rsp_bhs->status_detail = ISCSI_STATUS_MISSING_FIELDS;
                iscsi_conn->state = STATE_EXIT;
                return;
        }
        iscsi_conn->initiator = strdup(name);
        alias = iser_text_key_find(req_data, req_datasize, "InitiatorAlias");
        (void) alias;

        session_type = iser_text_key_find(req_data, req_datasize, "SessionType");
        target_name = iser_text_key_find(req_data, req_datasize, "TargetName");

        iscsi_conn->auth_method = -1;
        iscsi_conn->session_type = SESSION_NORMAL;

        if (session_type) {
                if (!strcmp(session_type, "Discovery"))
                        iscsi_conn->session_type = SESSION_DISCOVERY;
                else if (strcmp(session_type, "Normal")) {
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_INV_SESSION_TYPE;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }
        }

        if (iscsi_conn->session_type == SESSION_NORMAL) {
                struct iscsi_target *target = NULL;
                struct target_info tgt_info;

                if (!target_name) {
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_MISSING_FIELDS;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }

                memcpy(tgt_info.target_name, target_name, strlen(target_name) + 1);

                iser_target_create((void *)&tgt_info);
                if (tgt_info.ret) {
                        if (tgt_info.ret == ENOENT) {
                                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                                rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                                iscsi_conn->state = STATE_EXIT;
                        } else {
                                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                                rsp_bhs->status_detail = ISCSI_STATUS_MISSING_FIELDS;
                                iscsi_conn->state = STATE_EXIT;
                        }
                        return;
                }

                target = tgt_info.target;
                ret = block_reload(&target->fileid);
                if (ret) {
                        if (ret == ENOENT) {
                                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                                rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                                iscsi_conn->state = STATE_EXIT;
                                target_free(target);
                                return;
                        } else {
                                DERROR(""CHKID_FORMAT": %d:%s\n",
                                                CHKID_ARG(&target->fileid), ret, strerror(ret));
                        }
                }

                iscsi_conn->tid = target->tid;
                iscsi_conn->target = target;

		DWARN("the target fileid is %u\n", target->fileid.type);
		if (iser_netvip_is_vip(&iscsi_conn->self) || (iser_netvip_in_vipnet(&iscsi_conn->self) && !target_islocal(target))) {
			/**if (core_self())
				YASSERT(0);**/
			DWARN("------------------iscsi vip redirect-----------------\n");
			if (!iser_target_redirect(iscsi_conn, target)) {
				snprintf(buf, sizeof(buf), "%s:%s", target->redirect.addr, target->redirect.port);
				iser_text_key_add(iscsi_conn, tx_pdu, "TargetAddress", buf);
				rsp_bhs->status_class = ISCSI_STATUS_REDIRECT;
				rsp_bhs->status_detail = target->redirect.type;
				iscsi_conn->state = STATE_EXIT;
                                target_free(target);
				return;
			} else {
				DWARN("setup iscsi vip error!!!!!!!!!!\n");
                                rsp_bhs->flags = 0;
                                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
				rsp_bhs->status_detail = ISCSI_STATUS_MISSING_FIELDS;
				iscsi_conn->state = STATE_EXIT;
                                target_free(target);
                                return;
			}
		} else {

			DWARN("####################iscsi polling core redirect##################\n");

			if (!core_self()) {
				if (target_redirected(iscsi_conn, target)) {
					snprintf(buf, sizeof(buf), "%s:%s", target->redirect.addr, target->redirect.port);
					iser_text_key_add(iscsi_conn, tx_pdu, "TargetAddress", buf);
					rsp_bhs->status_class = ISCSI_STATUS_REDIRECT;
					rsp_bhs->status_detail = target->redirect.type;
					iscsi_conn->state = STATE_EXIT;
                                        target_free(target);
					return;
				}

			}
		}
                /*
                if (tgt_get_target_state(target->tid) != SCSI_TARGET_READY) {
                        rsp_bhs->status_class = ISCSI_STATUS_TARGET_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_TARGET_ERROR;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }

                if (ip_acl(iscsi_conn->tid, iscsi_conn)) {
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }

                if (iqn_acl(iscsi_conn->tid, iscsi_conn)) {
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }

                if (isns_scn_access(iscsi_conn->tid, name)) {
                        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                        rsp_bhs->status_detail = ISCSI_STATUS_TGT_NOT_FOUND;
                        iscsi_conn->state = STATE_EXIT;
                        return;
                }
                */

/*              if (iscsi_conn->target->max_sessions && */
/*                  (++iscsi_conn->target->session_cnt > iscsi_conn->target->max_sessions)) { */
/*                      iscsi_conn->target->session_cnt--; */
/*                      rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR; */
/*                      rsp_bhs->status_detail = ISCSI_STATUS_TOO_MANY_CONN; */
/*                      iscsi_conn->state = STATE_EXIT; */
/*                      return; */
/*              } */

                param_adjust_sess(iscsi_conn->session_param, &target->sess_param);

                iscsi_conn->exp_cmd_sn = be32_to_cpu(req_bhs->cmd_sn);
                iscsi_conn->max_cmd_sn = iscsi_conn->exp_cmd_sn;
                DINFO("set exp_cmdsn:0x%0x\n", iscsi_conn->exp_cmd_sn);
        }
        iser_text_key_add(iscsi_conn, tx_pdu, "TargetPortalGroupTag", "1");
}

/*
 * Allocate resources for this new connection.  Called after login, when
 * final negotiated transfer parameters are known.
 */
static int iser_login_complete(struct iscsi_conn *iscsi_conn)
{
        int ret = -1;
        struct iser_conn *conn = ISER_CONN(iscsi_conn);
        unsigned int irdsl, trdsl, outst_pdu, hdrsz;


        DINFO("entry\n");

        /* one more send, then done; login resources are left until then */
        iser_conn_login_phase_set(conn, LOGIN_PHASE_LAST_SEND);
        irdsl = iscsi_conn->session_param[key_initiator_recv_data_length].val;
        trdsl = iscsi_conn->session_param[key_target_recv_data_length].val;

        /* ToDo: outstanding pdus num */
        /* outst_pdu =
         * iscsi_conn->session_param[ISCSI_PARAM_MAX_OUTST_PDU].val; */

        /* hack, ib/ulp/iser does not have this param, but reading the code
         * shows their formula for max tx dtos outstanding
         *    = cmds_max * (1 + dataouts) + rx_misc + tx_misc
         */
#define ISER_INFLIGHT_DATAOUTS  0
#define ISER_MAX_RX_MISC_PDUS   4
#define ISER_MAX_TX_MISC_PDUS   6

        /* if (outst_pdu == 0) // ToDo: outstanding pdus num */
        outst_pdu =
                3 * ISER_MAX_QUEUE_CMD * (1 + ISER_INFLIGHT_DATAOUTS) +
                ISER_MAX_RX_MISC_PDUS + ISER_MAX_TX_MISC_PDUS;

        /* RDSLs do not include headers. */
        hdrsz = sizeof(struct iser_hdr) +
                sizeof(struct iscsi_hdr) +
                sizeof(struct iscsi_cdb_ahdr) +
                sizeof(struct iscsi_rlength_ahdr);

        if (trdsl < 1024)
                trdsl = 1024;
        conn->rsize = hdrsz + trdsl;
        /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
        (void) irdsl;
        /* conn->ssize = hdrsz + irdsl; */
        conn->ssize = conn->rsize;

        conn->max_outst_pdu = outst_pdu;

        ret = iser_conn_alloc_ff_resources(conn);
        if (ret)
                GOTO(err_ret, ret);
        /* How much data to grab in an RDMA operation, read or write */
        /* ToDo: fix iscsi login code, broken handling of MAX_XMIT_DL */
        iscsi_conn->session_param[key_max_xmit_data_length].val = membuf_size;

        return 0;
err_ret:
        return ret;
}

static void __target_connect__(void *arg)
{
        struct iscsi_conn *conn = arg;
        (void) target_connect(conn->session->target, inet_ntoa(conn->peer.sin_addr),
                              ntohs(conn->peer.sin_port));
}

static void iser_login_finish(struct iscsi_conn *iscsi_conn,
                              struct iser_pdu *tx_pdu)
{
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        int ret;
        uint8_t class, detail;

        switch (iscsi_conn->session_type) {
        case SESSION_NORMAL:
                /*
                 * update based on negotiations (but ep_login_complete
                 * could override)
                 */
                //iscsi_conn->data_inout_max_length =
                //iscsi_conn->session_param[key_max_xmit_data_length].val;

                /*
                 * Allocate transport resources for this connection.
                 */
                ret = iser_login_complete(iscsi_conn);
                if (ret) {
                        class = ISCSI_STATUS_TARGET_ERR;
                        detail = ISCSI_STATUS_NO_RESOURCES;
                        goto fail;
                }

                if (!iscsi_conn->session) {
                        ret = session_create(iscsi_conn);
                        if (ret) {
                                class = ISCSI_STATUS_TARGET_ERR;
                                detail = ISCSI_STATUS_TARGET_ERROR;
                                goto fail;
                        }

                        iscsi_conn->session->rdma = 1;

                        /* Register connection info to chunktable */
                        if (iscsi_conn->session->target) {
                                (void) cops->scan_lun(iscsi_conn);
                                schedule_task_new("iscsi_target_connect", __target_connect__, iscsi_conn, -1);
                        }
                } else {
                        /*
                        if (iscsi_conn->rdma ^ iscsi_conn->session->rdma) {
                                DERROR("new iscsi_conn rdma %d, but session %d\n",
                                        iscsi_conn->rdma, iscsi_conn->session->rdma);

                                class = ISCSI_STATUS_INITIATOR_ERR;
                                detail =ISCSI_LOGIN_STATUS_INVALID_REQUEST;
                                goto fail;
                        }
                        */
                }
                memcpy(iscsi_conn->sid.id.isid, iscsi_conn->session->sid.id.isid, sizeof(iscsi_conn->sid.id.isid));
                iscsi_conn->sid.id.tsih = iscsi_conn->session->sid.id.tsih;
                break;
        case SESSION_DISCOVERY:
                ret = iser_login_complete(iscsi_conn);
                if (ret) {
                        class = ISCSI_STATUS_TARGET_ERR;
                        detail = ISCSI_STATUS_NO_RESOURCES;
                        goto fail;
                }
                /* set a dummy sid.id.tsih value */
                iscsi_conn->sid.id.tsih = 1;
                break;
        }

        return;
fail:
        rsp_bhs->flags = 0;
        rsp_bhs->status_class = class;
        rsp_bhs->status_detail = detail;
        iscsi_conn->state = STATE_EXIT;
        return;
}

void iser_login_exec(struct iscsi_conn *iscsi_conn,
                     struct iser_pdu *rx_pdu,
                     struct iser_pdu *tx_pdu)
{
        struct iscsi_login_req_hdr *req_bhs = (struct iscsi_login_req_hdr *)rx_pdu->bhs;
        struct iscsi_login_rsp_hdr *rsp_bhs = (struct iscsi_login_rsp_hdr *)tx_pdu->bhs;
        int stay = 0, nsg_disagree = 0;

        tx_pdu->membuf.size = 0;

        memset(rsp_bhs, 0, BHS_SIZE);
        if ((req_bhs->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN_REQ ||
            !(req_bhs->opcode & ISCSI_OP_IMMEDIATE)) {
                /* reject */
        }

        rsp_bhs->opcode = ISCSI_OP_LOGIN_RSP;
        rsp_bhs->max_version = ISCSI_VERSION;
        rsp_bhs->active_version = ISCSI_VERSION;
        rsp_bhs->itt = req_bhs->itt;

        if (/* req_bhs->max_version < ISCSI_VERSION || */
            req_bhs->min_version > ISCSI_VERSION) {
                rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
                rsp_bhs->status_detail = ISCSI_STATUS_NO_VERSION;
                iscsi_conn->state = STATE_EXIT;
                return;
        }

        iscsi_conn->exp_cmd_sn = iscsi_conn->max_cmd_sn = ntohl(req_bhs->cmd_sn);

        switch (ISCSI_LOGIN_CURRENT_STAGE(req_bhs->flags)) {
        case ISCSI_SECURITY_NEGOTIATION_STAGE:
                DINFO("conn:%p Login request (security negotiation): %ld\n",
                        iscsi_conn, iscsi_conn->state);

                rsp_bhs->flags = ISCSI_SECURITY_NEGOTIATION_STAGE << 2;

                switch (iscsi_conn->state) {
                case STATE_READY:
                        iscsi_conn->state = STATE_SECURITY;
                        iser_login_start(iscsi_conn, rx_pdu, tx_pdu);

                        if (rsp_bhs->status_class)
                                return;
                        /* fall through */
                case STATE_SECURITY:
                        iser_login_security_scan(iscsi_conn, rx_pdu, tx_pdu);
                        DWARN("iscsi iser this is a test\n");
                        if (rsp_bhs->status_class)
                                return;
                        DWARN("iscsi iser this is a test2\n");
                        if (iscsi_conn->auth_method != AUTH_NONE) {
                                iscsi_conn->state = STATE_SECURITY_AUTH;
                                iscsi_conn->auth_state = AUTH_STATE_START;
                        }
                        DWARN("iscsi iser this is a test1\n");
                        break;
                case STATE_SECURITY_AUTH:
                        switch (iser_login_auth_exec(iscsi_conn, rx_pdu, tx_pdu)) {
                        case 0:
                                break;
                        default:
                        case -1:
                                goto init_err;
                        case -2:
                                goto auth_err;
                        }
                        break;
                default:
                        goto init_err;
                }

                break;
        case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
                DINFO("conn:%p Login request (operational negotiation): %ld\n",
                        iscsi_conn, iscsi_conn->state);
                rsp_bhs->flags = ISCSI_OP_PARMS_NEGOTIATION_STAGE << 2;

                switch (iscsi_conn->state) {
                case STATE_READY:
                        iscsi_conn->state = STATE_LOGIN;
                        iser_login_start(iscsi_conn, rx_pdu, tx_pdu);

                        if (!account_empty(iscsi_conn, AUTH_DIR_INCOMING)) {
                                goto auth_err;
                        }

                        if (rsp_bhs->status_class)
                                return;
                        iser_login_oper_scan(iscsi_conn, rx_pdu, tx_pdu);
                        DWARN("iscsi iser this is a test1\n");
                        if (rsp_bhs->status_class)
                                return;
                        DWARN("iscsi iser this is a test2\n");
                        stay = iser_login_check_params(iscsi_conn, tx_pdu);
                        DWARN("iscsi iser this is a test3\n");
                        break;
                case STATE_LOGIN:
                        iser_login_oper_scan(iscsi_conn, rx_pdu, tx_pdu);
                        if (rsp_bhs->status_class)
                                return;
                        stay = iser_login_check_params(iscsi_conn, tx_pdu);
                        break;
                default:
                        goto init_err;
                }
                break;
        default:
                goto init_err;
        }

        if (rsp_bhs->status_class)
                return;
        if (iscsi_conn->state != STATE_SECURITY_AUTH &&
            req_bhs->flags & ISCSI_FLG_TRANSIT) {
                int nsg = ISCSI_LOGIN_NEXT_STAGE(req_bhs->flags);

                switch (nsg) {
                case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
                        switch (iscsi_conn->state) {
                        case STATE_SECURITY:
                        case STATE_SECURITY_DONE:
                                iscsi_conn->state = STATE_SECURITY_LOGIN;
                                iser_login_security_done(iscsi_conn, rx_pdu, tx_pdu);
                                break;
                        default:
                                goto init_err;
                        }
                        break;
                case ISCSI_FULL_FEATURE_PHASE:
                        switch (iscsi_conn->state) {
                        case STATE_SECURITY:
                        case STATE_SECURITY_DONE:
                                if ((nsg_disagree = iser_login_check_params(iscsi_conn, tx_pdu))) {
                                        iscsi_conn->state = STATE_LOGIN;
                                        nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
                                        break;
                                }
                                iscsi_conn->state = STATE_SECURITY_FULL;
                                iser_login_security_done(iscsi_conn, rx_pdu, tx_pdu);
                                break;
                        case STATE_LOGIN:
                                if (stay)
                                        nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
                                else
                                        iscsi_conn->state = STATE_LOGIN_FULL;
                                break;
                        default:
                                goto init_err;
                        }
                        if (!stay && !nsg_disagree) {
                                iser_login_finish(iscsi_conn, tx_pdu);

                                if (rsp_bhs->status_class)
                                        return;
                        }
                        break;
                default:
                        goto init_err;
                }
                rsp_bhs->flags |= nsg | (stay ? 0 : ISCSI_FLG_TRANSIT);
        }

        if (iscsi_conn->exp_cmd_sn == ntohl(req_bhs->cmd_sn))
                iscsi_conn->exp_cmd_sn++;

        memcpy(rsp_bhs->sid.id.isid, iscsi_conn->sid.id.isid, sizeof(rsp_bhs->sid.id.isid));
        rsp_bhs->sid.id.tsih = iscsi_conn->sid.id.tsih;
        rsp_bhs->stat_sn = cpu_to_be32(iscsi_conn->stat_sn++);
        rsp_bhs->exp_cmd_sn = cpu_to_be32(iscsi_conn->exp_cmd_sn);
        rsp_bhs->max_cmd_sn = cpu_to_be32(iscsi_conn->max_cmd_sn);
        DWARN("iscsi iser this is a test4\n");
        return;

init_err:
        DERROR("conn:%p, Initiator error\n", iscsi_conn);
        rsp_bhs->flags = 0;
        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
        rsp_bhs->status_detail = ISCSI_STATUS_INIT_ERR;
        iscsi_conn->state = STATE_EXIT;
        return;

auth_err:
        DERROR("conn:%p, Authentication error\n", iscsi_conn);
        rsp_bhs->flags = 0;
        rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR;
        rsp_bhs->status_detail = ISCSI_STATUS_AUTH_FAILED;
        iscsi_conn->state = STATE_EXIT;
        return;
}

static void iser_target_list_build(struct iscsi_conn *conn, struct iser_pdu *tx_pdu, char *addr, char *name)
{
        struct uss_tgt_entry *tgt;
        struct list_head tgt_head;

        cops->scan_target(&tgt_head, conn);

        list_for_each_entry(tgt, &tgt_head, entry) {
                if (name && strcmp(tgt->iqn, name))
                        continue;
                else {
                        iser_text_key_add(conn, tx_pdu, "TargetName", tgt->iqn);
                        iser_text_key_add(conn, tx_pdu, "TargetAddress", addr);
                }
        }

        cops->free_target(&tgt_head);
}

static void iser_text_scan(struct iscsi_conn *iscsi_conn,
                           struct iser_pdu *rx_pdu,
                           struct iser_pdu *tx_pdu)
{
        char *key, *value, *data;
        int datasize;

        data = rx_pdu->membuf.addr;
        datasize = rx_pdu->membuf.size;

        while ((key = iser_text_next_key(&data, &datasize, &value))) {
                if (!strcmp(key, "SendTargets")) {
                        if (value[0] == 0)
                                continue;

                        struct sockaddr_storage ss;
                        socklen_t slen, blen;
                        char *p, buf[NI_MAXHOST + 128];

                        p = buf;
                        blen = sizeof(buf);

                        slen = sizeof(ss);
                        iscsi_conn->tp->ep_getsockname(iscsi_conn,
                                                       (struct sockaddr *) &ss,
                                                       &slen);
                        if (ss.ss_family == AF_INET6) {
                                *p++ = '[';
                                blen--;
                        }

                        slen = sizeof(ss);
                        getnameinfo((struct sockaddr *) &ss, slen, p, blen,
                                    NULL, 0, NI_NUMERICHOST);

                        p = buf + strlen(buf);

                        if (ss.ss_family == AF_INET6)
                                 *p++ = ']';

                        sprintf(p, ":%d,1", sanconf.iscsi_port);
                        iser_target_list_build(iscsi_conn, tx_pdu, buf,
                                          strcmp(value, "All") ? value : NULL);

                } else
                        iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood");
        }
}

int iser_text_exec(struct iscsi_conn *iscsi_conn,
                   struct iser_pdu *rx_pdu,
                   struct iser_pdu *tx_pdu)
{
        struct iscsi_text_req_hdr *req = (struct iscsi_text_req_hdr *)rx_pdu->bhs;
        struct iscsi_text_rsp_hdr *rsp = (struct iscsi_text_rsp_hdr *)tx_pdu->bhs;

        memset(rsp, 0, BHS_SIZE);

        if (be32_to_cpu(req->ttt) != 0xffffffff) {
                /* reject */;
        }
        rsp->opcode = ISCSI_OP_TEXT_RSP;
        rsp->itt = req->itt;
        /* rsp->ttt = rsp->ttt; */
        rsp->ttt = 0xffffffff;
        iscsi_conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn);
        if (!(req->opcode & ISCSI_OP_IMMEDIATE))
                iscsi_conn->exp_cmd_sn++;

        DINFO("Text request: %ld\n", iscsi_conn->state);
        iser_text_scan(iscsi_conn, rx_pdu, tx_pdu);

        if (tx_pdu->membuf.size > MAX_KEY_VALUE_PAIRS) {
                DERROR("Text pdu size %d too big, can't send at once\n",
                        tx_pdu->membuf.size);
                tx_pdu->membuf.size = 0;
        }

        if (req->flags & ISCSI_FLG_FINAL)
                rsp->flags = ISCSI_FLG_FINAL;

        rsp->stat_sn = cpu_to_be32(iscsi_conn->stat_sn++);
        rsp->exp_cmd_sn = cpu_to_be32(iscsi_conn->exp_cmd_sn);
        rsp->max_cmd_sn = cpu_to_be32(iscsi_conn->max_cmd_sn);

        return 0;
}
